/**
 * @author mrdoob / http://mrdoob.com/
 */

THREE.SpriteCanvasMaterial = function (parameters) {
  THREE.Material.call(this)

  this.type = 'SpriteCanvasMaterial'

  this.color = new THREE.Color(0xffffff)
  this.program = function () {}

  this.setValues(parameters)
}

THREE.SpriteCanvasMaterial.prototype = Object.create(THREE.Material.prototype)
THREE.SpriteCanvasMaterial.prototype.constructor = THREE.SpriteCanvasMaterial
THREE.SpriteCanvasMaterial.prototype.isSpriteCanvasMaterial = true

THREE.SpriteCanvasMaterial.prototype.clone = function () {
  var material = new THREE.SpriteCanvasMaterial()

  material.copy(this)
  material.color.copy(this.color)
  material.program = this.program

  return material
}

//

THREE.CanvasRenderer = function (parameters) {
  console.log('THREE.CanvasRenderer', THREE.REVISION)

  parameters = parameters || {}

  var _this = this
  var _renderData; var _elements; var _lights
  var _projector = new THREE.Projector()

  var _canvas = parameters.canvas !== undefined
				 ? parameters.canvas
				 : document.createElement('canvas')

  var _canvasWidth = _canvas.width
  var _canvasHeight = _canvas.height
  var _canvasWidthHalf = Math.floor(_canvasWidth / 2)
  var _canvasHeightHalf = Math.floor(_canvasHeight / 2)

  var _viewportX = 0
  var _viewportY = 0
  var _viewportWidth = _canvasWidth
  var _viewportHeight = _canvasHeight

  var _pixelRatio = 1

  var _context = _canvas.getContext('2d', {
    alpha: parameters.alpha === true
  })

  var _clearColor = new THREE.Color(0x000000)
  var _clearAlpha = parameters.alpha === true ? 0 : 1

  var _contextGlobalAlpha = 1
  var _contextGlobalCompositeOperation = 0
  var _contextStrokeStyle = null
  var _contextFillStyle = null
  var _contextLineWidth = null
  var _contextLineCap = null
  var _contextLineJoin = null
  var _contextLineDash = []

  var _v1; var _v2; var _v3

  var _v1x; var _v1y; var _v2x; var _v2y; var _v3x; var _v3y

  var _color = new THREE.Color()

  var _diffuseColor = new THREE.Color()
  var _emissiveColor = new THREE.Color()

  var _lightColor = new THREE.Color()

  var _patterns = {}

  var _uvs
  var _uv1x; var _uv1y; var _uv2x; var _uv2y; var _uv3x; var _uv3y

  var _clipBox = new THREE.Box2()
  var _clearBox = new THREE.Box2()
  var _elemBox = new THREE.Box2()

  var _ambientLight = new THREE.Color()
  var _directionalLights = new THREE.Color()
  var _pointLights = new THREE.Color()

  var _vector3 = new THREE.Vector3() // Needed for PointLight
  var _centroid = new THREE.Vector3()
  var _normal = new THREE.Vector3()
  var _normalViewMatrix = new THREE.Matrix3()

  /* TODO
	_canvas.mozImageSmoothingEnabled = false;
	_canvas.webkitImageSmoothingEnabled = false;
	_canvas.msImageSmoothingEnabled = false;
	_canvas.imageSmoothingEnabled = false;
	*/

  // dash+gap fallbacks for Firefox and everything else

  if (_context.setLineDash === undefined) {
    _context.setLineDash = function () {}
  }

  this.domElement = _canvas

  this.autoClear = true
  this.sortObjects = true
  this.sortElements = true

  this.info = {

    render: {

      vertices: 0,
      faces: 0

    }

  }

  // API

  this.getContext = function () {
    return _context
  }

  this.getContextAttributes = function () {
    return _context.getContextAttributes()
  }

  this.getPixelRatio = function () {
    return _pixelRatio
  }

  this.setPixelRatio = function (value) {
    if (value !== undefined) _pixelRatio = value
  }

  this.setSize = function (width, height, updateStyle) {
    _canvasWidth = width * _pixelRatio
    _canvasHeight = height * _pixelRatio

    _canvas.width = _canvasWidth
    _canvas.height = _canvasHeight

    _canvasWidthHalf = Math.floor(_canvasWidth / 2)
    _canvasHeightHalf = Math.floor(_canvasHeight / 2)

    if (updateStyle !== false) {
      _canvas.style.width = width + 'px'
      _canvas.style.height = height + 'px'
    }

    _clipBox.min.set(-_canvasWidthHalf, -_canvasHeightHalf)
    _clipBox.max.set(_canvasWidthHalf, _canvasHeightHalf)

    _clearBox.min.set(-_canvasWidthHalf, -_canvasHeightHalf)
    _clearBox.max.set(_canvasWidthHalf, _canvasHeightHalf)

    _contextGlobalAlpha = 1
    _contextGlobalCompositeOperation = 0
    _contextStrokeStyle = null
    _contextFillStyle = null
    _contextLineWidth = null
    _contextLineCap = null
    _contextLineJoin = null

    this.setViewport(0, 0, width, height)
  }

  this.setViewport = function (x, y, width, height) {
    _viewportX = x * _pixelRatio
    _viewportY = y * _pixelRatio

    _viewportWidth = width * _pixelRatio
    _viewportHeight = height * _pixelRatio
  }

  this.setScissor = function () {}
  this.setScissorTest = function () {}

  this.setClearColor = function (color, alpha) {
    _clearColor.set(color)
    _clearAlpha = alpha !== undefined ? alpha : 1

    _clearBox.min.set(-_canvasWidthHalf, -_canvasHeightHalf)
    _clearBox.max.set(_canvasWidthHalf, _canvasHeightHalf)
  }

  this.setClearColorHex = function (hex, alpha) {
    console.warn('THREE.CanvasRenderer: .setClearColorHex() is being removed. Use .setClearColor() instead.')
    this.setClearColor(hex, alpha)
  }

  this.getClearColor = function () {
    return _clearColor
  }

  this.getClearAlpha = function () {
    return _clearAlpha
  }

  this.getMaxAnisotropy = function () {
    return 0
  }

  this.clear = function () {
    if (_clearBox.isEmpty() === false) {
      _clearBox.intersect(_clipBox)
      _clearBox.expandByScalar(2)

      _clearBox.min.x = _clearBox.min.x + _canvasWidthHalf
      _clearBox.min.y = -_clearBox.min.y + _canvasHeightHalf		// higher y value !
      _clearBox.max.x = _clearBox.max.x + _canvasWidthHalf
      _clearBox.max.y = -_clearBox.max.y + _canvasHeightHalf		// lower y value !

      if (_clearAlpha < 1) {
        _context.clearRect(
          _clearBox.min.x | 0,
          _clearBox.max.y | 0,
          (_clearBox.max.x - _clearBox.min.x) | 0,
          (_clearBox.min.y - _clearBox.max.y) | 0
        )
      }

      if (_clearAlpha > 0) {
        setOpacity(1)
        setBlending(THREE.NormalBlending)

        setFillStyle('rgba(' + Math.floor(_clearColor.r * 255) + ',' + Math.floor(_clearColor.g * 255) + ',' + Math.floor(_clearColor.b * 255) + ',' + _clearAlpha + ')')

        _context.fillRect(
          _clearBox.min.x | 0,
          _clearBox.max.y | 0,
          (_clearBox.max.x - _clearBox.min.x) | 0,
          (_clearBox.min.y - _clearBox.max.y) | 0
        )
      }

      _clearBox.makeEmpty()
    }
  }

  // compatibility

  this.clearColor = function () {}
  this.clearDepth = function () {}
  this.clearStencil = function () {}

  this.render = function (scene, camera) {
    if (camera.isCamera === undefined) {
      console.error('THREE.CanvasRenderer.render: camera is not an instance of THREE.Camera.')
      return
    }

    var background = scene.background

    if (background && background.isColor) {
      setOpacity(1)
      setBlending(THREE.NormalBlending)

      setFillStyle(background.getStyle())
      _context.fillRect(0, 0, _canvasWidth, _canvasHeight)
    } else if (this.autoClear === true) {
      this.clear()
    }

    _this.info.render.vertices = 0
    _this.info.render.faces = 0

    _context.setTransform(_viewportWidth / _canvasWidth, 0, 0, -_viewportHeight / _canvasHeight, _viewportX, _canvasHeight - _viewportY)
    _context.translate(_canvasWidthHalf, _canvasHeightHalf)

    _renderData = _projector.projectScene(scene, camera, this.sortObjects, this.sortElements)
    _elements = _renderData.elements
    _lights = _renderData.lights

    _normalViewMatrix.getNormalMatrix(camera.matrixWorldInverse)

    /* DEBUG
		setFillStyle( 'rgba( 0, 255, 255, 0.5 )' );
		_context.fillRect( _clipBox.min.x, _clipBox.min.y, _clipBox.max.x - _clipBox.min.x, _clipBox.max.y - _clipBox.min.y );
		*/

    calculateLights()

    for (var e = 0, el = _elements.length; e < el; e++) {
      var element = _elements[e]

      var material = element.material

      if (material === undefined || material.opacity === 0) continue

      _elemBox.makeEmpty()

      if (element instanceof THREE.RenderableSprite) {
        _v1 = element
        _v1.x *= _canvasWidthHalf; _v1.y *= _canvasHeightHalf

        renderSprite(_v1, element, material)
      } else if (element instanceof THREE.RenderableLine) {
        _v1 = element.v1; _v2 = element.v2

        _v1.positionScreen.x *= _canvasWidthHalf; _v1.positionScreen.y *= _canvasHeightHalf
        _v2.positionScreen.x *= _canvasWidthHalf; _v2.positionScreen.y *= _canvasHeightHalf

        _elemBox.setFromPoints([
          _v1.positionScreen,
          _v2.positionScreen
        ])

        if (_clipBox.intersectsBox(_elemBox) === true) {
          renderLine(_v1, _v2, element, material)
        }
      } else if (element instanceof THREE.RenderableFace) {
        _v1 = element.v1; _v2 = element.v2; _v3 = element.v3

        if (_v1.positionScreen.z < -1 || _v1.positionScreen.z > 1) continue
        if (_v2.positionScreen.z < -1 || _v2.positionScreen.z > 1) continue
        if (_v3.positionScreen.z < -1 || _v3.positionScreen.z > 1) continue

        _v1.positionScreen.x *= _canvasWidthHalf; _v1.positionScreen.y *= _canvasHeightHalf
        _v2.positionScreen.x *= _canvasWidthHalf; _v2.positionScreen.y *= _canvasHeightHalf
        _v3.positionScreen.x *= _canvasWidthHalf; _v3.positionScreen.y *= _canvasHeightHalf

        if (material.overdraw > 0) {
          expand(_v1.positionScreen, _v2.positionScreen, material.overdraw)
          expand(_v2.positionScreen, _v3.positionScreen, material.overdraw)
          expand(_v3.positionScreen, _v1.positionScreen, material.overdraw)
        }

        _elemBox.setFromPoints([
          _v1.positionScreen,
          _v2.positionScreen,
          _v3.positionScreen
        ])

        if (_clipBox.intersectsBox(_elemBox) === true) {
          renderFace3(_v1, _v2, _v3, 0, 1, 2, element, material)
        }
      }

      /* DEBUG
			setLineWidth( 1 );
			setStrokeStyle( 'rgba( 0, 255, 0, 0.5 )' );
			_context.strokeRect( _elemBox.min.x, _elemBox.min.y, _elemBox.max.x - _elemBox.min.x, _elemBox.max.y - _elemBox.min.y );
			*/

      _clearBox.union(_elemBox)
    }

    /* DEBUG
		setLineWidth( 1 );
		setStrokeStyle( 'rgba( 255, 0, 0, 0.5 )' );
		_context.strokeRect( _clearBox.min.x, _clearBox.min.y, _clearBox.max.x - _clearBox.min.x, _clearBox.max.y - _clearBox.min.y );
		*/

    _context.setTransform(1, 0, 0, 1, 0, 0)
  }

  //

  function calculateLights () {
    _ambientLight.setRGB(0, 0, 0)
    _directionalLights.setRGB(0, 0, 0)
    _pointLights.setRGB(0, 0, 0)

    for (var l = 0, ll = _lights.length; l < ll; l++) {
      var light = _lights[l]
      var lightColor = light.color

      if (light.isAmbientLight) {
        _ambientLight.add(lightColor)
      } else if (light.isDirectionalLight) {
        // for sprites

        _directionalLights.add(lightColor)
      } else if (light.isPointLight) {
        // for sprites

        _pointLights.add(lightColor)
      }
    }
  }

  function calculateLight (position, normal, color) {
    for (var l = 0, ll = _lights.length; l < ll; l++) {
      var light = _lights[l]

      _lightColor.copy(light.color)

      if (light.isDirectionalLight) {
        var lightPosition = _vector3.setFromMatrixPosition(light.matrixWorld).normalize()

        var amount = normal.dot(lightPosition)

        if (amount <= 0) continue

        amount *= light.intensity

        color.add(_lightColor.multiplyScalar(amount))
      } else if (light.isPointLight) {
        var lightPosition = _vector3.setFromMatrixPosition(light.matrixWorld)

        var amount = normal.dot(_vector3.subVectors(lightPosition, position).normalize())

        if (amount <= 0) continue

        amount *= light.distance == 0 ? 1 : 1 - Math.min(position.distanceTo(lightPosition) / light.distance, 1)

        if (amount == 0) continue

        amount *= light.intensity

        color.add(_lightColor.multiplyScalar(amount))
      }
    }
  }

  function renderSprite (v1, element, material) {
    setOpacity(material.opacity)
    setBlending(material.blending)

    var scaleX = element.scale.x * _canvasWidthHalf
    var scaleY = element.scale.y * _canvasHeightHalf

    var dist = Math.sqrt(scaleX * scaleX + scaleY * scaleY) // allow for rotated sprite
    _elemBox.min.set(v1.x - dist, v1.y - dist)
    _elemBox.max.set(v1.x + dist, v1.y + dist)

    if (material.isSpriteMaterial) {
      var texture = material.map

      if (texture !== null) {
        var pattern = _patterns[texture.id]

        if (pattern === undefined || pattern.version !== texture.version) {
          pattern = textureToPattern(texture)
          _patterns[texture.id] = pattern
        }

        if (pattern.canvas !== undefined) {
          setFillStyle(pattern.canvas)

          var bitmap = texture.image

          var ox = bitmap.width * texture.offset.x
          var oy = bitmap.height * texture.offset.y

          var sx = bitmap.width * texture.repeat.x
          var sy = bitmap.height * texture.repeat.y

          var cx = scaleX / sx
          var cy = scaleY / sy

          _context.save()
          _context.translate(v1.x, v1.y)
          if (material.rotation !== 0) _context.rotate(material.rotation)
          _context.translate(-scaleX / 2, -scaleY / 2)
          _context.scale(cx, cy)
          _context.translate(-ox, -oy)
          _context.fillRect(ox, oy, sx, sy)
          _context.restore()
        }
      } else {
        // no texture

        setFillStyle(material.color.getStyle())

        _context.save()
        _context.translate(v1.x, v1.y)
        if (material.rotation !== 0) _context.rotate(material.rotation)
        _context.scale(scaleX, -scaleY)
        _context.fillRect(-0.5, -0.5, 1, 1)
        _context.restore()
      }
    } else if (material.isSpriteCanvasMaterial) {
      setStrokeStyle(material.color.getStyle())
      setFillStyle(material.color.getStyle())

      _context.save()
      _context.translate(v1.x, v1.y)
      if (material.rotation !== 0) _context.rotate(material.rotation)
      _context.scale(scaleX, scaleY)

      material.program(_context)

      _context.restore()
    } else if (material.isPointsMaterial) {
      setFillStyle(material.color.getStyle())

      _context.save()
      _context.translate(v1.x, v1.y)
      if (material.rotation !== 0) _context.rotate(material.rotation)
      _context.scale(scaleX * material.size, -scaleY * material.size)
      _context.fillRect(-0.5, -0.5, 1, 1)
      _context.restore()
    }

    /* DEBUG
		setStrokeStyle( 'rgb(255,255,0)' );
		_context.beginPath();
		_context.moveTo( v1.x - 10, v1.y );
		_context.lineTo( v1.x + 10, v1.y );
		_context.moveTo( v1.x, v1.y - 10 );
		_context.lineTo( v1.x, v1.y + 10 );
		_context.stroke();
		*/
  }

  function renderLine (v1, v2, element, material) {
    setOpacity(material.opacity)
    setBlending(material.blending)

    _context.beginPath()
    _context.moveTo(v1.positionScreen.x, v1.positionScreen.y)
    _context.lineTo(v2.positionScreen.x, v2.positionScreen.y)

    if (material.isLineBasicMaterial) {
      setLineWidth(material.linewidth)
      setLineCap(material.linecap)
      setLineJoin(material.linejoin)

      if (material.vertexColors !== THREE.VertexColors) {
        setStrokeStyle(material.color.getStyle())
      } else {
        var colorStyle1 = element.vertexColors[0].getStyle()
        var colorStyle2 = element.vertexColors[1].getStyle()

        if (colorStyle1 === colorStyle2) {
          setStrokeStyle(colorStyle1)
        } else {
          try {
            var grad = _context.createLinearGradient(
              v1.positionScreen.x,
              v1.positionScreen.y,
              v2.positionScreen.x,
              v2.positionScreen.y
            )
            grad.addColorStop(0, colorStyle1)
            grad.addColorStop(1, colorStyle2)
          } catch (exception) {
            grad = colorStyle1
          }

          setStrokeStyle(grad)
        }
      }

      if (material.isLineDashedMaterial) {
        setLineDash([material.dashSize, material.gapSize])
      }

      _context.stroke()
      _elemBox.expandByScalar(material.linewidth * 2)

      if (material.isLineDashedMaterial) {
        setLineDash([])
      }
    }
  }

  function renderFace3 (v1, v2, v3, uv1, uv2, uv3, element, material) {
    _this.info.render.vertices += 3
    _this.info.render.faces++

    setOpacity(material.opacity)
    setBlending(material.blending)

    _v1x = v1.positionScreen.x; _v1y = v1.positionScreen.y
    _v2x = v2.positionScreen.x; _v2y = v2.positionScreen.y
    _v3x = v3.positionScreen.x; _v3y = v3.positionScreen.y

    drawTriangle(_v1x, _v1y, _v2x, _v2y, _v3x, _v3y)

    if ((material.isMeshLambertMaterial || material.isMeshPhongMaterial || material.isMeshStandardMaterial) && material.map === null) {
      _diffuseColor.copy(material.color)
      _emissiveColor.copy(material.emissive)

      if (material.vertexColors === THREE.FaceColors) {
        _diffuseColor.multiply(element.color)
      }

      _color.copy(_ambientLight)

      _centroid.copy(v1.positionWorld).add(v2.positionWorld).add(v3.positionWorld).divideScalar(3)

      calculateLight(_centroid, element.normalModel, _color)

      _color.multiply(_diffuseColor).add(_emissiveColor)

      material.wireframe === true
				 ? strokePath(_color, material.wireframeLinewidth, material.wireframeLinecap, material.wireframeLinejoin)
				 : fillPath(_color)
    } else if (material.isMeshBasicMaterial || material.isMeshLambertMaterial || material.isMeshPhongMaterial || material.isMeshStandardMaterial) {
      if (material.map !== null) {
        var mapping = material.map.mapping

        if (mapping === THREE.UVMapping) {
          _uvs = element.uvs
          patternPath(_v1x, _v1y, _v2x, _v2y, _v3x, _v3y, _uvs[uv1].x, _uvs[uv1].y, _uvs[uv2].x, _uvs[uv2].y, _uvs[uv3].x, _uvs[uv3].y, material.map)
        }
      } else if (material.envMap !== null) {
        if (material.envMap.mapping === THREE.SphericalReflectionMapping) {
          _normal.copy(element.vertexNormalsModel[uv1]).applyMatrix3(_normalViewMatrix)
          _uv1x = 0.5 * _normal.x + 0.5
          _uv1y = 0.5 * _normal.y + 0.5

          _normal.copy(element.vertexNormalsModel[uv2]).applyMatrix3(_normalViewMatrix)
          _uv2x = 0.5 * _normal.x + 0.5
          _uv2y = 0.5 * _normal.y + 0.5

          _normal.copy(element.vertexNormalsModel[uv3]).applyMatrix3(_normalViewMatrix)
          _uv3x = 0.5 * _normal.x + 0.5
          _uv3y = 0.5 * _normal.y + 0.5

          patternPath(_v1x, _v1y, _v2x, _v2y, _v3x, _v3y, _uv1x, _uv1y, _uv2x, _uv2y, _uv3x, _uv3y, material.envMap)
        }
      } else {
        _color.copy(material.color)

        if (material.vertexColors === THREE.FaceColors) {
          _color.multiply(element.color)
        }

        material.wireframe === true
					 ? strokePath(_color, material.wireframeLinewidth, material.wireframeLinecap, material.wireframeLinejoin)
					 : fillPath(_color)
      }
    } else if (material.isMeshNormalMaterial) {
      _normal.copy(element.normalModel).applyMatrix3(_normalViewMatrix)

      _color.setRGB(_normal.x, _normal.y, _normal.z).multiplyScalar(0.5).addScalar(0.5)

      material.wireframe === true
				 ? strokePath(_color, material.wireframeLinewidth, material.wireframeLinecap, material.wireframeLinejoin)
				 : fillPath(_color)
    } else {
      _color.setRGB(1, 1, 1)

      material.wireframe === true
				 ? strokePath(_color, material.wireframeLinewidth, material.wireframeLinecap, material.wireframeLinejoin)
				 : fillPath(_color)
    }
  }

  //

  function drawTriangle (x0, y0, x1, y1, x2, y2) {
    _context.beginPath()
    _context.moveTo(x0, y0)
    _context.lineTo(x1, y1)
    _context.lineTo(x2, y2)
    _context.closePath()
  }

  function strokePath (color, linewidth, linecap, linejoin) {
    setLineWidth(linewidth)
    setLineCap(linecap)
    setLineJoin(linejoin)
    setStrokeStyle(color.getStyle())

    _context.stroke()

    _elemBox.expandByScalar(linewidth * 2)
  }

  function fillPath (color) {
    setFillStyle(color.getStyle())
    _context.fill()
  }

  function textureToPattern (texture) {
    if (texture.version === 0 ||
			texture instanceof THREE.CompressedTexture ||
			texture instanceof THREE.DataTexture) {
      return {
        canvas: undefined,
        version: texture.version
      }
    }

    var image = texture.image

    if (image.complete === false) {
      return {
        canvas: undefined,
        version: 0
      }
    }

    var repeatX = texture.wrapS === THREE.RepeatWrapping || texture.wrapS === THREE.MirroredRepeatWrapping
    var repeatY = texture.wrapT === THREE.RepeatWrapping || texture.wrapT === THREE.MirroredRepeatWrapping

    var mirrorX = texture.wrapS === THREE.MirroredRepeatWrapping
    var mirrorY = texture.wrapT === THREE.MirroredRepeatWrapping

    //

    var canvas = document.createElement('canvas')
    canvas.width = image.width * (mirrorX ? 2 : 1)
    canvas.height = image.height * (mirrorY ? 2 : 1)

    var context = canvas.getContext('2d')
    context.setTransform(1, 0, 0, -1, 0, image.height)
    context.drawImage(image, 0, 0)

    if (mirrorX === true) {
      context.setTransform(-1, 0, 0, -1, image.width, image.height)
      context.drawImage(image, -image.width, 0)
    }

    if (mirrorY === true) {
      context.setTransform(1, 0, 0, 1, 0, 0)
      context.drawImage(image, 0, image.height)
    }

    if (mirrorX === true && mirrorY === true) {
      context.setTransform(-1, 0, 0, 1, image.width, 0)
      context.drawImage(image, -image.width, image.height)
    }

    var repeat = 'no-repeat'

    if (repeatX === true && repeatY === true) {
      repeat = 'repeat'
    } else if (repeatX === true) {
      repeat = 'repeat-x'
    } else if (repeatY === true) {
      repeat = 'repeat-y'
    }

    var pattern = _context.createPattern(canvas, repeat)

    if (texture.onUpdate) texture.onUpdate(texture)

    return {
      canvas: pattern,
      version: texture.version
    }
  }

  function patternPath (x0, y0, x1, y1, x2, y2, u0, v0, u1, v1, u2, v2, texture) {
    var pattern = _patterns[texture.id]

    if (pattern === undefined || pattern.version !== texture.version) {
      pattern = textureToPattern(texture)
      _patterns[texture.id] = pattern
    }

    if (pattern.canvas !== undefined) {
      setFillStyle(pattern.canvas)
    } else {
      setFillStyle('rgba( 0, 0, 0, 1)')
      _context.fill()
      return
    }

    // http://extremelysatisfactorytotalitarianism.com/blog/?p=2120

    var a; var b; var c; var d; var e; var f; var det; var idet
    var offsetX = texture.offset.x / texture.repeat.x
    var offsetY = texture.offset.y / texture.repeat.y
    var width = texture.image.width * texture.repeat.x
    var height = texture.image.height * texture.repeat.y

    u0 = (u0 + offsetX) * width
    v0 = (v0 + offsetY) * height

    u1 = (u1 + offsetX) * width
    v1 = (v1 + offsetY) * height

    u2 = (u2 + offsetX) * width
    v2 = (v2 + offsetY) * height

    x1 -= x0; y1 -= y0
    x2 -= x0; y2 -= y0

    u1 -= u0; v1 -= v0
    u2 -= u0; v2 -= v0

    det = u1 * v2 - u2 * v1

    if (det === 0) return

    idet = 1 / det

    a = (v2 * x1 - v1 * x2) * idet
    b = (v2 * y1 - v1 * y2) * idet
    c = (u1 * x2 - u2 * x1) * idet
    d = (u1 * y2 - u2 * y1) * idet

    e = x0 - a * u0 - c * v0
    f = y0 - b * u0 - d * v0

    _context.save()
    _context.transform(a, b, c, d, e, f)
    _context.fill()
    _context.restore()
  }

  /*
	function clipImage( x0, y0, x1, y1, x2, y2, u0, v0, u1, v1, u2, v2, image ) {

		// http://extremelysatisfactorytotalitarianism.com/blog/?p=2120

		var a, b, c, d, e, f, det, idet,
		width = image.width - 1,
		height = image.height - 1;

		u0 *= width; v0 *= height;
		u1 *= width; v1 *= height;
		u2 *= width; v2 *= height;

		x1 -= x0; y1 -= y0;
		x2 -= x0; y2 -= y0;

		u1 -= u0; v1 -= v0;
		u2 -= u0; v2 -= v0;

		det = u1 * v2 - u2 * v1;

		idet = 1 / det;

		a = ( v2 * x1 - v1 * x2 ) * idet;
		b = ( v2 * y1 - v1 * y2 ) * idet;
		c = ( u1 * x2 - u2 * x1 ) * idet;
		d = ( u1 * y2 - u2 * y1 ) * idet;

		e = x0 - a * u0 - c * v0;
		f = y0 - b * u0 - d * v0;

		_context.save();
		_context.transform( a, b, c, d, e, f );
		_context.clip();
		_context.drawImage( image, 0, 0 );
		_context.restore();

	}
	*/

  // Hide anti-alias gaps

  function expand (v1, v2, pixels) {
    var x = v2.x - v1.x; var y = v2.y - v1.y
    var det = x * x + y * y; var idet

    if (det === 0) return

    idet = pixels / Math.sqrt(det)

    x *= idet; y *= idet

    v2.x += x; v2.y += y
    v1.x -= x; v1.y -= y
  }

  // Context cached methods.

  function setOpacity (value) {
    if (_contextGlobalAlpha !== value) {
      _context.globalAlpha = value
      _contextGlobalAlpha = value
    }
  }

  function setBlending (value) {
    if (_contextGlobalCompositeOperation !== value) {
      if (value === THREE.NormalBlending) {
        _context.globalCompositeOperation = 'source-over'
      } else if (value === THREE.AdditiveBlending) {
        _context.globalCompositeOperation = 'lighter'
      } else if (value === THREE.SubtractiveBlending) {
        _context.globalCompositeOperation = 'darker'
      } else if (value === THREE.MultiplyBlending) {
        _context.globalCompositeOperation = 'multiply'
      }

      _contextGlobalCompositeOperation = value
    }
  }

  function setLineWidth (value) {
    if (_contextLineWidth !== value) {
      _context.lineWidth = value
      _contextLineWidth = value
    }
  }

  function setLineCap (value) {
    // "butt", "round", "square"

    if (_contextLineCap !== value) {
      _context.lineCap = value
      _contextLineCap = value
    }
  }

  function setLineJoin (value) {
    // "round", "bevel", "miter"

    if (_contextLineJoin !== value) {
      _context.lineJoin = value
      _contextLineJoin = value
    }
  }

  function setStrokeStyle (value) {
    if (_contextStrokeStyle !== value) {
      _context.strokeStyle = value
      _contextStrokeStyle = value
    }
  }

  function setFillStyle (value) {
    if (_contextFillStyle !== value) {
      _context.fillStyle = value
      _contextFillStyle = value
    }
  }

  function setLineDash (value) {
    if (_contextLineDash.length !== value.length) {
      _context.setLineDash(value)
      _contextLineDash = value
    }
  }
}
